package a5;

import java.util.List;

import a5.common.ProgramCursor;
import a5.common.Token;
import a5.environment.Environment;
import a5.environment.EnvironmentImpl;
import a5.grammar.grammars.G1;
import a5.grammar.interfaces.Grammar;

import com.google.inject.Inject;

/**
 * A pluggable {@link a5.grammar.interfaces.Grammar Grammar} interpreter. This class will accept
 * and interpret any {@code Grammar} in any {@link com.a5.Environment Environment}. To ensure 
 * thread safety regardless of Grammar and Environment mutability, this class keeps a 
 * {@code ThreadLocal} copy of the environment and grammar instances. 
 * 
 * @author Jason Campos
 * @version Assignment 5, CS 152 (Programming Paradigms) Fall 2012, Jeffrey Smith
 */
public class Interpreter {
	/** The environment to use during interpretation. One copy per thread. */
	private ThreadLocal<Environment> environment = new ThreadLocal<>();
	/** The grammar to apply during interpretation. One copy per thread. */
	private ThreadLocal<Grammar> grammar = new ThreadLocal<>();
	
	/**
	 * This constructor is only for retaining the API. It should not be used since 
	 * the grammar and environment are hard coded. To make better use of this class, 
	 * use {@link #Interpreter(Environment, Grammar)} and an injection framework such as
	 * Google Guice.
	 */
	@Deprecated
	public Interpreter(){
		this(new EnvironmentImpl(), new G1());
	}
	
	/**
	 * <pre>
	 * Creates a new Interpreter for interpreting strings prurportedly in the argument {@link a5.grammar.interfaces.Grammar Grammar} 
	 * using the argument {@link a5.environment.Environment Environment}. 
	 * 
	 * This constructor uses the {@code @Inject} annotation for use with Google Guice's dependency injection framework.
	 * It is however acceptable to invoke this constructor using the {@code new} keyword.
	 * 
	 * To see this in action, run A5Injected.java
	 * </pre>
	 * 
	 * @param environment
	 * The {@link a5.environment.Environment Environment} which emplores variable and function saving and scoping policies to 
	 * use during interpretation.
	 * @param grammar
	 * The {@link a5.grammar.interfaces.Grammar Grammar} to apply during interpretation.
	 */
	@Inject
	public Interpreter(Environment environment, Grammar grammar){
		this.environment.set(environment);
		this.grammar.set(grammar);
	}
	
	/**
	 * Interprets the argument program and returns the result of the interpretation as a {@code String}.
	 * 
	 * @param program
	 * A {@code String} program purportedly in this interpreter's associated {@link #grammar}.
	 *  
	 * @return
	 * The {@code String} result of the interpretation of the argument program. 
	 */
	public String interpret(String program){
		// Tokenize the program and append an end of input token.
		// The end of input token will be used after interpretation to verify that the
		// program ended at the expected location.
		List<Token> tokens = Token.tokenize(program);
		tokens.add(new Token(Token.END_OF_INPUT_TYPE));
		ProgramCursor cursor = new ProgramCursor(tokens);
		
		// Perform the interpretation. The grammar defines which productions to apply. 
		Environment env = environment.get();
		String result = grammar.get().interpret(env, cursor).toString();
		
		// If the program is in the grammar, then the end of input token should be the last remaining token
		cursor.match(env, Token.END_OF_INPUT_TYPE);
		return result;
	}
}
